// Copyright (c) 2019-present, iQIYI, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

// Created by caikelun on 2019-03-07.

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreserved-id-macro"
#define _GNU_SOURCE
#pragma clang diagnostic pop

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <inttypes.h>
#include <signal.h>
#include <ucontext.h>
#include <unwind.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include "xcc_errno.h"
#include "xcc_util.h"
#include "xcc_unwind.h"
#include "xcc_meminfo.h"
#include "xcc_fmt.h"
#include "xcc_version.h"
#include "xc_fallback.h"
#include "xc_common.h"
#include "xc_util.h"

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-statement-expression"

static size_t xc_fallback_get_process_thread(char *buf, size_t len, pid_t tid) {
  char tname[64];

  xcc_util_get_thread_name(tid, tname, sizeof(tname));

  return xcc_fmt_snprintf(buf, len, "pid: %d, tid: %d, name: %s  >>> %s <<<\n",
                          xc_common_process_id, tid, tname, xc_common_process_name);
}

static size_t xc_fallback_get_signal(char *buf, size_t len, siginfo_t *si) {
  //fault addr
  char addr_desc[64];
  if (xcc_util_signal_has_si_addr(si))
    xcc_fmt_snprintf(addr_desc, sizeof(addr_desc), "%p", si->si_addr);
  else
    xcc_fmt_snprintf(addr_desc, sizeof(addr_desc), "--------");

  //from
  char sender_desc[64] = "";
  if (xcc_util_signal_has_sender(si, xc_common_process_id))
    xcc_fmt_snprintf(sender_desc,
                     sizeof(sender_desc),
                     " from pid %d, uid %d",
                     si->si_pid,
                     si->si_uid);

  return xcc_fmt_snprintf(buf, len, "signal %d (%s), code %d (%s%s), fault addr %s\n",
                          si->si_signo, xcc_util_get_signame(si),
                          si->si_code, xcc_util_get_sigcodename(si), sender_desc, addr_desc);
}

static size_t xc_fallback_get_regs(char *buf, size_t len, ucontext_t *uc) {
#if defined(__arm__)
  return xcc_fmt_snprintf(buf, len,
                          "    r0  %08x  r1  %08x  r2  %08x  r3  %08x\n"
                          "    r4  %08x  r5  %08x  r6  %08x  r7  %08x\n"
                          "    r8  %08x  r9  %08x  r10 %08x  r11 %08x\n"
                          "    ip  %08x  sp  %08x  lr  %08x  pc  %08x\n\n",
                          uc->uc_mcontext.arm_r0,
                          uc->uc_mcontext.arm_r1,
                          uc->uc_mcontext.arm_r2,
                          uc->uc_mcontext.arm_r3,
                          uc->uc_mcontext.arm_r4,
                          uc->uc_mcontext.arm_r5,
                          uc->uc_mcontext.arm_r6,
                          uc->uc_mcontext.arm_r7,
                          uc->uc_mcontext.arm_r8,
                          uc->uc_mcontext.arm_r9,
                          uc->uc_mcontext.arm_r10,
                          uc->uc_mcontext.arm_fp,
                          uc->uc_mcontext.arm_ip,
                          uc->uc_mcontext.arm_sp,
                          uc->uc_mcontext.arm_lr,
                          uc->uc_mcontext.arm_pc);
#elif defined(__aarch64__)
  return xcc_fmt_snprintf(buf, len,
                          "    x0  %016lx  x1  %016lx  x2  %016lx  x3  %016lx\n"
                          "    x4  %016lx  x5  %016lx  x6  %016lx  x7  %016lx\n"
                          "    x8  %016lx  x9  %016lx  x10 %016lx  x11 %016lx\n"
                          "    x12 %016lx  x13 %016lx  x14 %016lx  x15 %016lx\n"
                          "    x16 %016lx  x17 %016lx  x18 %016lx  x19 %016lx\n"
                          "    x20 %016lx  x21 %016lx  x22 %016lx  x23 %016lx\n"
                          "    x24 %016lx  x25 %016lx  x26 %016lx  x27 %016lx\n"
                          "    x28 %016lx  x29 %016lx\n"
                          "    sp  %016lx  lr  %016lx  pc  %016lx\n\n",
                          uc->uc_mcontext.regs[0],
                          uc->uc_mcontext.regs[1],
                          uc->uc_mcontext.regs[2],
                          uc->uc_mcontext.regs[3],
                          uc->uc_mcontext.regs[4],
                          uc->uc_mcontext.regs[5],
                          uc->uc_mcontext.regs[6],
                          uc->uc_mcontext.regs[7],
                          uc->uc_mcontext.regs[8],
                          uc->uc_mcontext.regs[9],
                          uc->uc_mcontext.regs[10],
                          uc->uc_mcontext.regs[11],
                          uc->uc_mcontext.regs[12],
                          uc->uc_mcontext.regs[13],
                          uc->uc_mcontext.regs[14],
                          uc->uc_mcontext.regs[15],
                          uc->uc_mcontext.regs[16],
                          uc->uc_mcontext.regs[17],
                          uc->uc_mcontext.regs[18],
                          uc->uc_mcontext.regs[19],
                          uc->uc_mcontext.regs[20],
                          uc->uc_mcontext.regs[21],
                          uc->uc_mcontext.regs[22],
                          uc->uc_mcontext.regs[23],
                          uc->uc_mcontext.regs[24],
                          uc->uc_mcontext.regs[25],
                          uc->uc_mcontext.regs[26],
                          uc->uc_mcontext.regs[27],
                          uc->uc_mcontext.regs[28],
                          uc->uc_mcontext.regs[29],
                          uc->uc_mcontext.sp,
                          uc->uc_mcontext.regs[30], //lr
                          uc->uc_mcontext.pc);
#elif defined(__i386__)
  return xcc_fmt_snprintf(buf, len,
                          "    eax %08x  ebx %08x  ecx %08x  edx %08x\n"
                          "    edi %08x  esi %08x\n"
                          "    ebp %08x  esp %08x  eip %08x\n\n",
                          uc->uc_mcontext.gregs[REG_EAX],
                          uc->uc_mcontext.gregs[REG_EBX],
                          uc->uc_mcontext.gregs[REG_ECX],
                          uc->uc_mcontext.gregs[REG_EDX],
                          uc->uc_mcontext.gregs[REG_EDI],
                          uc->uc_mcontext.gregs[REG_ESI],
                          uc->uc_mcontext.gregs[REG_EBP],
                          uc->uc_mcontext.gregs[REG_ESP],
                          uc->uc_mcontext.gregs[REG_EIP]);
#elif defined(__x86_64__)
  return xcc_fmt_snprintf(buf, len,
                          "    rax %016lx  rbx %016lx  rcx %016lx  rdx %016lx\n"
                          "    r8  %016lx  r9  %016lx  r10 %016lx  r11 %016lx\n"
                          "    r12 %016lx  r13 %016lx  r14 %016lx  r15 %016lx\n"
                          "    rdi %016lx  rsi %016lx\n"
                          "    rbp %016lx  rsp %016lx  rip %016lx\n\n",
                          uc->uc_mcontext.gregs[REG_RAX],
                          uc->uc_mcontext.gregs[REG_RBX],
                          uc->uc_mcontext.gregs[REG_RCX],
                          uc->uc_mcontext.gregs[REG_RDX],
                          uc->uc_mcontext.gregs[REG_R8],
                          uc->uc_mcontext.gregs[REG_R9],
                          uc->uc_mcontext.gregs[REG_R10],
                          uc->uc_mcontext.gregs[REG_R11],
                          uc->uc_mcontext.gregs[REG_R12],
                          uc->uc_mcontext.gregs[REG_R13],
                          uc->uc_mcontext.gregs[REG_R14],
                          uc->uc_mcontext.gregs[REG_R15],
                          uc->uc_mcontext.gregs[REG_RDI],
                          uc->uc_mcontext.gregs[REG_RSI],
                          uc->uc_mcontext.gregs[REG_RBP],
                          uc->uc_mcontext.gregs[REG_RSP],
                          uc->uc_mcontext.gregs[REG_RIP]);
#endif
}

static size_t xc_fallback_get_backtrace(char *buf, size_t len, siginfo_t *si, ucontext_t *uc) {
  size_t used = 0;

  used += xcc_fmt_snprintf(buf + used, len - used, "backtrace:\n");
  used += xcc_unwind_get(xc_common_api_level, si, uc, buf + used, len - used);
  if (used >= len - 1) {
    buf[len - 3] = '\n';
    buf[len - 2] = '\0';
    used = len - 2;
  }
  used += xcc_fmt_snprintf(buf + used, len - used, "\n");
  return used;
}

size_t xc_fallback_get_emergency(siginfo_t *si,
                                 ucontext_t *uc,
                                 pid_t tid,
                                 uint64_t crash_time,
                                 char *emergency,
                                 size_t emergency_len) {
  size_t used = xcc_util_get_dump_header(emergency, emergency_len,
                                         XCC_UTIL_CRASH_TYPE_NATIVE,
                                         xc_common_time_zone,
                                         xc_common_start_time,
                                         crash_time,
                                         xc_common_app_id,
                                         xc_common_app_version,
                                         xc_common_api_level,
                                         xc_common_os_version,
                                         xc_common_kernel_version,
                                         xc_common_abi_list,
                                         xc_common_manufacturer,
                                         xc_common_brand,
                                         xc_common_model,
                                         xc_common_build_fingerprint);
  used += xc_fallback_get_process_thread(emergency + used, emergency_len - used, tid);
  used += xc_fallback_get_signal(emergency + used, emergency_len - used, si);
  used += xc_fallback_get_regs(emergency + used, emergency_len - used, uc);
  used += xc_fallback_get_backtrace(emergency + used, emergency_len - used, si, uc);
  return used;
}

int xc_fallback_record(int log_fd,
                       char *emergency,
                       unsigned int logcat_system_lines,
                       unsigned int logcat_events_lines,
                       unsigned int logcat_main_lines,
                       int dump_fds,
                       int dump_network_info) {
  int r;

  if (log_fd < 0) return XCC_ERRNO_INVAL;

  if (0 != (r = xcc_util_write_str(log_fd, emergency))) return r;

  //If we wrote the emergency info successfully, we don't need to return it from callback again.
  emergency[0] = '\0';

  if (0 != (r = xcc_util_record_logcat(log_fd,
                                       xc_common_process_id,
                                       xc_common_api_level,
                                       logcat_system_lines,
                                       logcat_events_lines,
                                       logcat_main_lines)))
    return r;
  if (dump_fds)
    if (0 != (r = xcc_util_record_fds(log_fd, xc_common_process_id))) return r;
  if (dump_network_info)
    if (0
        != (r = xcc_util_record_network_info(log_fd, xc_common_process_id, xc_common_api_level)))
      return r;
  if (0 != (r = xcc_meminfo_record(log_fd, xc_common_process_id))) return r;

  return 0;
}

#pragma clang diagnostic pop
